<?php

namespace ric\auth;

use think\facade\Db;
use think\facade\Session;
use think\facade\Cache;

/**
 * 权限认证类
 * 功能特性：
 * 1，是对规则进行认证，不是对节点进行认证。用户可以把节点当作规则名称实现对节点进行认证。
 *      $auth=new Auth();  $auth->check('规则名称','用户id')
 * 2，可以同时对多条规则进行认证，并设置多条规则的关系（or或者and）
 *      $auth=new Auth();  $auth->check('规则1,规则2','用户id','and')
 *      第三个参数为and时表示，用户需要同时具有规则1和规则2的权限。 当第三个参数为or时，表示用户值需要具备其中一个条件即可。默认为or
 * 3，一个用户可以属于多个用户组(think_auth_group_access表 定义了用户所属用户组)。我们需要设置每个用户组拥有哪些规则(think_auth_group 定义了用户组权限)
 * 4，支持规则表达式。
 *      在think_auth_rule 表中定义一条规则时，如果type为1， condition字段就可以定义规则表达式。 如定义{score}>5  and {score}<100  表示用户的分数在5-100之间时这条规则才会通过。
 */

class Auth
{
    /**
     * @var object 对象实例
     */
    protected static $instance;

    /**
     * 当前请求实例
     * @var Request
     */
    protected $request;

    //默认配置
    protected $config = [
        'auth_on'           => 1, // 权限开关
        'auth_type'         => 1, // 认证方式，1为实时认证；2为登录认证。
        'auth_group'        => 'auth_group', // 用户组数据表名
        'auth_group_access' => 'auth_group_access', // 用户-用户组关系表
        'auth_rule'         => 'auth_rule', // 权限规则表
        'auth_user'         => 'admin', // 用户信息表
        'auth_user_pk'      => 'id',    // 用户表ID字段名
        'auth_log'          => 'auth_log',    // 用户操作日志表
        'auth_rule_unified' => false,
        'auth_token_name' => 'X-token',
		'url'             => '',
		'is_auth_unified' => true,
		'is_user_unified' => false,
    ];

    protected $getname = 'admin';

    /**
     * 类架构函数
     * Auth constructor.
     */
    public function __construct($name,$website_id=0)
    {
        $this->getname = $name;
        $this->website_id = $website_id;
		
        //可设置配置项 auth, 此配置项为数组。
        if ($auth = config('auth.'.$this->getname)) {
            $this->config = array_merge($this->config, $auth);
        }
		
		//判断是否想要重制id
		$token = Session::getId(false);
		$cache_website = cache($this->getname.'.auth_website.'.$token);
		
		if( $cache_website > 0){
			$this->website_id = $cache_website;
			cache($this->getname.'.auth_website.'.$token,$this->website_id,43200);
		}
		
        // 初始化request
        $this->request = request();
    }

    /**
     * 初始化
     * @access public
     * @param array $options 参数
     * @return \think\Request
     */
    public static function instance($options = [],$website_id=0)
    {
        if (is_null(self::$instance)) {
            self::$instance = new static($options,$website_id);
        }

        return self::$instance;
    }

    /**
     * 检查权限
     * @param        $name     string|array  需要验证的规则列表,支持逗号分隔的权限规则或索引数组
     * @param        $uid      int           认证用户的id
     * @param int    $type     认证类型
     * @param string $mode     执行check的模式
     * @param string $relation 如果为 'or' 表示满足任一条规则即通过验证;如果为 'and'则表示需满足所有规则才能通过验证
     * @return bool               通过验证返回true;失败返回false
     */
    public function check($name, $uid, $type = 1, $mode = 'url', $relation = 'or')
    {
        if (!$this->config['auth_on']) {
            return true;
        }
        // 获取用户需要验证的所有有效规则列表
        $authList = $this->getAuthList($uid, $type);
        if (is_string($name)) {
            $name = strtolower($name);
            if (strpos($name, ',') !== false) {
                $name = explode(',', $name);
            } else {
                $name = [$name];
            }
        }
        $list = []; //保存验证通过的规则名
        if ('url' == $mode) {
            $REQUEST = unserialize(strtolower(serialize($this->request->param())));
        }
        foreach ($authList as $auth) {
            $query = preg_replace('/^.+\?/U', '', $auth);
            if ('url' == $mode && $query != $auth) {
                parse_str($query, $param); //解析规则中的param
                $intersect = array_intersect_assoc($REQUEST, $param);
                $auth      = preg_replace('/\?.*$/U', '', $auth);
                if (in_array($auth, $name) && $intersect == $param) {
                    //如果节点相符且url参数满足
                    $list[] = $auth;
                }
            } else {
                if (in_array($auth, $name)) {
                    $list[] = $auth;
                }
            }
        }
        if ('or' == $relation && !empty($list)) {
            return true;
        }
        $diff = array_diff($name, $list);
        if ('and' == $relation && empty($diff)) {
            return true;
        }

        return false;
    }

    /**
     * 判断用户登录，并返回所有信息，写入session
     * @param string $account 用户账号
     * @param string $password 密码
     * @return array 用户信息
     */
    public function getLogin($account,$password){
        $user = Db::name($this->config['auth_user'])
            ->where('website_id', '=', $this->website_id)
            ->where('account', '=', $account)
            ->where('password', '=', $password)
            ->where('is_delete', '=', 0)->fetchSql(false)->find();
// dd($user);
        if(empty($user)){
            $this->getLogout();
            return [];
        }

        session('login.'.$this->getname.$this->website_id,$user[$this->config['auth_user_pk']]);
        session($this->getname.'.userinfo'.$this->website_id,$user); //下一个命令要用
		$token = Session::getId(false);
		cache($this->getname.'.auth_website.'.$token,$this->website_id,43200);
//        $user['groups'] = $this->getGroups($user[$this->config['auth_user_pk']]);
//        $user['authlist'] = $this->getAuthList($user[$this->config['auth_user_pk']],-1);
//        $user['rule'] = $this->getRuleIds($user[$this->config['auth_user_pk']]);
        $user['menu'] = $this->getRuleMenu($this->getRuleIds($user[$this->config['auth_user_pk']]));
        $user[$this->config['auth_token_name']] = $token;
        $user['password'] = '******************';
        session($this->getname.'.userinfo'.$this->website_id,$user);
        Session::save();

        return $user;
    }

    /**
     * 用户退出
     * @param string $account 用户账号
     * @param string $password 密码
     * @return array 用户信息
     */
    public function getLogout(){
		$token = Session::getId(false);
		Cache::delete($this->getname.'.auth_website.'.$token); 
        Session::delete('login.'.$this->getname.$this->website_id);
        Session::delete($this->getname.'.userinfo'.$this->website_id);
		Session::clear();
		Session::save();
        return '退出成功';
    }

    /**
     * 获得用户资料,根据自己的情况读取数据库
     * @param integer $uid 用户ID
     * @return array 用户信息
     */
    public function getUserInfo()
    {
        $userinfo = session($this->getname.'.userinfo'.$this->website_id);
        return $userinfo;
    }

    /**
     * 写操作日志
     */
    public function setLog($message)
    {
        $userinfo = $this->getUserInfo();
        if($userinfo){
            $rule = $this->getRule();
            $operation = array_column($rule,null,'name');
            $controller = request()->controller();
            $action = request()->action();

            $data = [
                'userid' => $userinfo['id'],      //用户账号
                'name'   => $userinfo['name'],
                'source' => request()->url(),   //来源
                'route' => $controller . '-' . $action,    //路由
                'operation' => $message?:($operation[$controller . '-' . $action]??''),//动作
                'param' => request()->except(['s', '_pjax']),    //请求参数
                'ip' => request()->ip(),       //ip
                'website_id' => $this->website_id
            ];
            return Db::name($this->config['auth_log'])->json(['param'])->insert($data);
        }
    }

    /**
     * 获取所有权限明显表
     */
    public function getRule()
    {
        if(!$this->config['is_auth_unified'] && !$this->config['auth_rule_unified']){
            $auth_rule = cache($this->getname.'.auth_rule'.$this->website_id)?:[];
            if(empty($auth_rule)){
                $auth_rule = Db::name($this->config['auth_rule'])->where('website_id','=',$this->website_id)->where('status','=',1)->select()->toArray();
                cache($this->getname.'.auth_rule'.$this->website_id, $auth_rule, 3600);
            }
        }else{
            $auth_rule = cache($this->getname.'.auth_rule0')?:[];
            if(empty($auth_rule)){
                $auth_rule = Db::name($this->config['auth_rule'])->where('status','=',1)->select()->toArray();
                cache($this->getname.'.auth_rule0', $auth_rule, 3600);
            }
        }

        return $auth_rule;
    }

    /**
     * 修改用户密码
     */
    public function setPwd($old,$new)
    {
        $id = session('login.'.$this->getname.$this->website_id);
        $upnum = Db::name($this->config['auth_user'])
            ->where('id', '=', $id)
            ->where('password', '=', $old)
            ->update(['password' => $new]);
        return $upnum;
    }

    /**
     * 用户组列表
     */
    public function getGroupAll($current_page,$per_page)
    {
        $where = [];
        if(!$this->config['auth_rule_unified']){
            $where['website_id'] = $this->website_id;
        }
        $list = Db::name($this->config['auth_group'])
            ->where($where)->withoutField('password')->paginate([
                'list_rows'=> $per_page?:10,       //每页数量
                'var_page' => 'page',   //分页变量
                'page'     => $current_page?:1,        //当前页面
            ]);
        return $list->toArray();
    }

    /**
     * 创建用户组
     */
    public function addGroup($data)
    {
        $group = [];
        if(!$this->config['auth_rule_unified']){
            $group['website_id'] = $this->website_id;
        }
        $group['title'] = $data['title'];
        $group['status'] = isset($data['status']) ? $data['status'] : 1 ;
//        $group['rules'] = $data['rules'];
        $group['create_time'] = date('Y-m-d H:i:s', time());
        $group['update_time'] = date('Y-m-d H:i:s', time());

        $whereExist['title'] = $data['title'];
        $whereExist['website_id'] = $this->website_id;

        $count = Db::name($this->config['auth_group'])->where($whereExist)->count();
        if ($count > 0) {
            return false;
        }

        return Db::name($this->config['auth_group'])->insert($group);
    }

    /**
     * 修改用户组
     */
    public function editGroup($data)
    {
        $group = [];
        $where['id'] = $data['id'];
        if(!$this->config['auth_rule_unified']){
            $group['website_id'] = $this->website_id;
        }
        $group['title'] = $data['title'];
        $group['status'] = isset($data['status']) ? $data['status'] : 1 ;
//        $group['rules'] = $data['rules'];
        $group['update_time'] = date('Y-m-d H:i:s', time());

        $whereExist['title'] = $data['title'];
        $whereExist['website_id'] = $this->website_id;

        $count = Db::name($this->config['auth_group'])->where($whereExist)->find();
        if (isset($count['id']) && $count['id'] != $data['id']) {
            return false;
        }

        return Db::name($this->config['auth_group'])->where($where)->update($group);
    }

    /**
     * 删除用户组
     */
    public function delGroup($id)
    {
        $where['id'] = $id;

        $whereGroup['group_id'] = $id;

        $count = Db::name($this->config['auth_group_access'])->where($whereGroup)->count();
        if ($count > 0) {
            return false;
        }

        return Db::name($this->config['auth_group'])->where($where)->delete();
    }

    /**
     * 设置用户组限
     */
    public function setGroupRule($data)
    {
        $where['id'] = $data['id'];
        $rule['rules'] = $data['rules'];

        return Db::name($this->config['auth_group'])->where($where)->update($data);

    }

    /**
     * 用户列表
     */
    public function getUserAll($current_page,$per_page, $where1 = [])
    {
		$where = [];
        if(!$this->config['auth_rule_unified']){
            $where['website_id'] = $this->website_id;
        }
        $list = Db::name($this->config['auth_user'])
            ->where('is_delete', '=', 0)->withoutField('password')->where($where)->where($where1)->paginate([
                'list_rows'=> $per_page?:10,       //每页数量
                'var_page' => 'page',   //分页变量
                'page'     => $current_page?:1,        //当前页面
            ]);
        $user = $list->toArray();

        $whereUserGroup[] = array('a.uid', 'IN', implode(',', array_column($user['data'], 'id')));
        $userGroup = Db::name($this->config['auth_group_access'])->alias('a')
            ->join($this->config['auth_group'] . ' b', 'a.group_id = b.id', 'left')
            ->where($whereUserGroup)->field('a.group_id, b.title as group_name, a.uid')->select()->toArray();

        $uGroupId = array_column($userGroup, 'group_id', 'uid');

        foreach ($user['data'] as &$value) {

            $groupId = '';
            $groupName = '';

            foreach ($userGroup as $g => $group) {
                ($group['uid'] == $value['id']) ? $groupId .= $group['group_id'] . ',' : '' ;
                ($group['uid'] == $value['id']) ? $groupName .= $group['group_name'] . ',' : '' ;
            }
            $value['group_id'] = (substr($groupId,0,strlen($groupId)-1) === false) ? '' : substr($groupId,0,strlen($groupId)-1);
            $value['group_name'] = (substr($groupName,0,strlen($groupName)-1) === false) ? '' : substr($groupName,0,strlen($groupName)-1);

        }

        return $user;
    }

    /**
     * 添加用户
     */
    public function addUser($user)
    {

        if (isset($user['X-token'])) {
            unset($user['X-token']);
        }
        if (isset($user['X-time'])) {
            unset($user['X-time']);
        }
        if (isset($user['X-sigin'])) {
            unset($user['X-sigin']);
        }

        if (!isset($user['group_id'])) {
            return false;
        }

        $user['website_id'] = 0;

        if(!$this->config['auth_rule_unified']){
            $user['website_id'] = $this->website_id;
        }

        $user['create_time'] = date('Y-m-d H:i:s', time());
        $user['update_time'] = date('Y-m-d H:i:s', time());

        $whereExist = [];
        $whereExist['account'] = $user['account'];
        if(!$this->config['is_user_unified'] && !$this->config['auth_rule_unified']){
            $whereExist['website_id'] = $user['website_id'];
        }
        $count = Db::name($this->config['auth_user'])->where($whereExist)->count();
        if ($count > 0) {
            return false;
        }

        $uid = Db::name($this->config['auth_user'])->strict(false)->insertGetId($user);

        $userGroup['uid'] = $uid;
        $userGroup['group_id'] = $user['group_id'];
        $userGroup['create_time'] = date('Y-m-d H:i:s', time());

        return Db::name($this->config['auth_group_access'])->strict(false)->insert($userGroup);
    }

    /**
     * 编辑用户
     */
    public function editUser($data)
    {
        if (isset($data['X-token'])) {
            unset($data['X-token']);
        }
        if (isset($data['X-time'])) {
            unset($data['X-time']);
        }
        if (isset($data['X-sigin'])) {
            unset($data['X-sigin']);
        }

        if (!isset($data['group_id'])) {
            return false;
        }

        if (!isset($data['password']) || $data['password'] == '') {
            unset($data['password']);
        }

        $data['website_id'] = 0;

        if(!$this->config['auth_rule_unified']){
            $data['website_id'] = $this->website_id;
        }

        $where['id'] = $data['id'];
        if (isset($data['password']) && $data['password'] != '') {
            $data['password'] = $data['password'];
        }
        $data['update_time'] = date('Y-m-d H:i:s', time());

        $whereExist = [];
        $whereExist['account'] = $data['account'];
        if(!$this->config['is_user_unified'] && !$this->config['auth_rule_unified']){
            $whereExist['website_id'] = $this->website_id;
        }
        $count = Db::name($this->config['auth_user'])->where($whereExist)->find();
        if (isset($count['id']) && $count['id'] != $data['id']) {
            return false;
        }
        Db::name($this->config['auth_user'])->where($where)->strict(false)->update($data);

        $whereGroup['uid'] = $data['id'];
        $userGroup['group_id'] = $data['group_id'];

        $groupCount = Db::name($this->config['auth_group_access'])->where($whereGroup)->count();
        if ($groupCount > 0) {
            Db::name($this->config['auth_group_access'])->where($whereGroup)->strict(false)->update($userGroup);
        } else {
            $userGroup['uid'] = $data['id'];
            $userGroup['group_id'] = $data['group_id'];
            $userGroup['create_time'] = date('Y-m-d H:i:s', time());
            Db::name($this->config['auth_group_access'])->strict(false)->insert($userGroup);
        }

        return true;
    }

    /**
     * 删除用户
     */
    public function delUser($id)
    {
        $where['id'] = $id;
        $data['is_delete'] = 1;
        return Db::name($this->config['auth_user'])->where($where)->update($data);
    }




    /**
     * 根据用户id获取用户组,返回值为数组
     * @param  $uid int     用户id
     * @return array       用户所属的用户组 array(
     *              array('uid'=>'用户id','group_id'=>'用户组id','title'=>'用户组名称','rules'=>'用户组拥有的规则id,多个,号隔开'),
     *              ...)
     */
    protected function getGroups($uid)
    {
        static $groups = [];
        if (isset($groups[$uid])) {
            return $groups[$uid];
        }
        // 转换表名
        $auth_group_access = $this->config['auth_group_access'];
        $auth_group        = $this->config['auth_group'];
        // 执行查询

//        if(!$this->config['auth_rule_unified']){
//            $map[] = [$auth_group . '.website_id', '=', $this->website_id];
//        }
        $map[] = [$auth_group . '.status', '=', 1];
        $map[] = [$auth_group_access . '.uid', '=', $uid];
        $user_groups  = Db::view($auth_group_access, 'uid,group_id')
            ->view($auth_group, 'title,rules', "{$auth_group_access}.group_id={$auth_group}.id", 'LEFT')
            ->where($map)
            ->select()->toArray();
        $groups[$uid] = $user_groups ?: [];

        return $groups[$uid];
    }

    /**
     * 获得权限列表
     * @param integer $uid 用户id
     * @param integer $type
     * @return array
     */
    protected function getAuthList($uid, $type)
    {
        static $_authList = []; //保存用户验证通过的权限列表
        $t = implode(',', (array)$type);
        if (isset($_authList[$uid . $t])) {
            return $_authList[$uid . $t];
        }
        if (2 == $this->config['auth_type'] && session('_auth_list_'.$this->website_id . $uid . $t)) {
            return session('_auth_list_'.$this->website_id . $uid . $t);
        }
        //读取用户所属用户组
        $groups = $this->getGroups($uid);
        $ids    = []; //保存用户所属用户组设置的所有权限规则id
        foreach ($groups as $g) {
            $ids = array_merge($ids, explode(',', trim($g['rules'], ',')));
        }
        $ids = array_unique($ids);
        if (empty($ids)) {
            $_authList[$uid . $t] = [];

            return [];
        }

        if(!$this->config['is_auth_unified'] && !$this->config['auth_rule_unified']){
            $map[] = ['website_id', '=', $this->website_id];
        }

        if($type == -1){
            $map[] = ['id', 'in', $ids];
            $map[] = ['status', '=', 1];

        }else{
            $map[] = ['id', 'in', $ids];
            $map[] = ['type', '=', $type];
            $map[] = ['status', '=', 1];
        }

        //读取用户组所有权限规则

        $rules = Db::name($this->config['auth_rule'])->where($map)->field('condition,name,title,front')->select();
        //循环规则，判断结果。
        $authList = []; //

        foreach ($rules as $rule) {
            if (!empty($rule['condition'])) {
                //根据condition进行验证
                $user    = $this->getUserInfo(); //获取用户信息,一维数组
                $command = preg_replace('/\{(\w*?)\}/', '$user[\'\\1\']', $rule['condition']);
                //dump($command); //debug
                $condition = null;
                @(eval('$condition =(' . $command . ');'));
                if ($condition) {
                    $authList[] = strtolower($rule['name']);
                }
            } else {
                //只要存在就记录
                $authList[] = strtolower($rule['name']);
            }
        }
        $_authList[$uid . $t] = $authList;
        if (2 == $this->config['auth_type']) {
            //规则列表结果保存到session
            session('_auth_list_'.$this->website_id . $uid . $t, $authList);
        }
        return array_unique($authList);
    }

    /**
     * 获取权限组对应的权限列表
     * @param     $gid
     * @param int $type
     * @return array|mixed
     */
    protected function getGroupAuthList($gid, $type = 1)
    {
        // 转换表名
        $auth_group = $this->config['auth_group'];
        $auth_rule  = $this->config['auth_rule'];
        // 执行查询
        $where = [
            ['website_id', '=', $this->website_id],
            ['status', '=', 1],
            ['id', '=', $gid],
        ];
        $rules = Db::name($auth_group)->where($where)->value('rules');
        // 格式化access表id
        $ids = explode(',', trim($rules, ','));
        $ids = array_unique($ids);

        if(!$this->config['is_auth_unified'] && !$this->config['auth_rule_unified']){
            $map[] = ['website_id', '=', $this->website_id];
        }

        if($type == -1){
            $map[] = ['id', 'in', $ids];
            $map[] = ['status', '=', 1];

        }else{
            $map[] = ['id', 'in', $ids];
            $map[] = ['type', '=', $type];
            $map[] = ['status', '=', 1];
        }

        //读取用户组所有权限规则
        $rules = Db::name($auth_rule)->where($map)->column('title,name,condition');

        return $rules;
    }

    /**
     * 获取用户所有权限
     * @param $uid 用户ID
     * @return array 权限ID数组
     */
    protected function getRuleIds($uid)
    {
        //读取用户所属用户组
        $groups = $this->getGroups($uid);
        $ids    = []; //保存用户所属用户组设置的所有权限规则id
        foreach ($groups as $g) {
            $ids = array_merge($ids, explode(',', trim($g['rules'], ',')));
        }
        $ids = array_unique($ids);
        return $ids;
    }

    /**
     * 获取用户权限数
     * @param $uid 用户ID
     * @return array 权限ID数组
     */
    protected function getRuleMenu($ruleIds)
    {

        if(!$this->config['is_auth_unified'] && !$this->config['auth_rule_unified']){
            $map[] = ['website_id', '=', $this->website_id];
        }
        $map[] = ['id', 'in', $ruleIds];

        $rules = Db::name($this->config['auth_rule'])
            ->where($map)
            ->field('id,name,title,pid,front,menu')
            ->select()->toArray();
        return $rules;
    }
	
	/**
	 * 获取配置文件
	 */
	public function getConfig()
	{
	    return $this->config;
	}

}
